package cn.kennylee.codehub.mybatis.das.extension;

import cn.hutool.core.collection.CollUtil;
import cn.kennylee.codehub.mybatis.das.eo.BaseEo;
import cn.kennylee.codehub.mybatis.das.utils.DasHelper;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.metadata.TableFieldInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfo;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.baomidou.mybatisplus.core.toolkit.Assert;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.conditions.query.ChainQuery;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.query.QueryChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.ChainUpdate;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.UpdateChainWrapper;
import com.baomidou.mybatisplus.extension.toolkit.ChainWrappers;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import com.github.yulichang.query.MPJQueryWrapper;
import jakarta.annotation.Resource;
import lombok.Getter;
import lombok.Setter;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.session.SqlSession;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.transaction.annotation.Transactional;

import javax.sql.DataSource;
import java.io.Serializable;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;

/**
 * <p> 基础DAS层 </p>
 * <p>Created on 2024/5/15.</p>
 *
 * @author kennylee
 * @see com.baomidou.mybatisplus.extension.service.IService
 * @since 0.0.1
 */
public abstract class ComBaseDas<T extends BaseEo<P>, P extends Serializable> {

    protected Log log = LogFactory.getLog(getClass());

    @Resource
    private DataSource dataSource;

    /**
     * 默认批次提交数量
     */
    @Setter
    @Getter
    public int defaultBatchSize = 1000;

    /**
     * 插入一条记录（选择字段，策略插入）
     *
     * @param entity 实体对象
     */
    public boolean save(T entity) {
        return SqlHelper.retBool(getBaseMapper().insert(entity));
    }

    /**
     * 插入（批量）
     *
     * @param entityList 实体对象集合
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean saveBatch(Collection<T> entityList) {
        return saveBatch(entityList, defaultBatchSize);
    }

    /**
     * 插入（批量）
     *
     * @param entityList 实体对象集合
     * @param batchSize  分片插入的长度，根据实际业务和数据库限制进行配置
     */
    public abstract boolean saveBatch(Collection<T> entityList, int batchSize);

    protected SqlSession sqlSessionBatch() {
        return SqlHelper.sqlSessionBatch(this.currentModelClass());
    }

    protected abstract Class<T> currentModelClass();

    /**
     * 批量修改插入
     *
     * @param entityList 实体对象集合
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean saveOrUpdateBatch(Collection<T> entityList) {
        return saveOrUpdateBatch(entityList, defaultBatchSize);
    }

    /**
     * 批量修改插入
     *
     * @param entityList 实体对象集合
     * @param batchSize  每次的数量
     */
    public abstract boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize);

    /**
     * 根据 ID 删除
     *
     * @param id 主键ID
     */
    public boolean removeById(P id) {
        return SqlHelper.retBool(getBaseMapper().deleteById(id));
    }

    /**
     * 根据 ID 删除
     *
     * @param id      主键(类型必须与实体类型字段保持一致)
     * @param useFill 是否启用填充(为true的情况,会将入参转换实体进行delete删除)
     * @return 删除结果
     * @since 3.5.0
     */
    public boolean removeById(P id, boolean useFill) {
        throw new UnsupportedOperationException("不支持的方法!");
    }

    /**
     * 根据实体(ID)删除
     *
     * @param entity 实体
     * @since 3.4.4
     */
    public boolean removeById(T entity) {
        return SqlHelper.retBool(getBaseMapper().deleteById(entity));
    }

    /**
     * 根据 columnMap 条件，删除记录
     *
     * @param columnMap 表字段 map 对象
     */
    public boolean removeByMap(Map<String, Object> columnMap) {
        Assert.notEmpty(columnMap, "error: columnMap must not be empty");
        return SqlHelper.retBool(getBaseMapper().deleteByMap(columnMap));
    }

    /**
     * 根据 entity 条件，删除记录
     *
     * @param queryWrapper 实体包装类 {@link QueryWrapper}
     */
    public boolean remove(Wrapper<T> queryWrapper) {
        return SqlHelper.retBool(getBaseMapper().delete(queryWrapper));
    }

    /**
     * 删除（根据ID 批量删除）
     *
     * @param list 主键ID或实体列表
     */
    public boolean removeByIds(Collection<P> list) {
        if (CollectionUtils.isEmpty(list)) {
            return false;
        }
        return SqlHelper.retBool(getBaseMapper().deleteBatchIds(list));
    }

    /**
     * 批量删除
     *
     * @param list    主键ID或实体列表
     * @param useFill 是否填充(为true的情况,会将入参转换实体进行delete删除)
     * @return 删除结果
     * @since 3.5.0
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean removeByIds(Collection<P> list, boolean useFill) {
        if (CollectionUtils.isEmpty(list)) {
            return false;
        }
        if (useFill) {
            return removeBatchByIds(list, true);
        }
        return SqlHelper.retBool(getBaseMapper().deleteBatchIds(list));
    }

    /**
     * 批量删除(jdbc批量提交)
     *
     * @param list 主键ID或实体列表(主键ID类型必须与实体类型字段保持一致)
     * @return 删除结果
     * @since 3.5.0
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean removeBatchByIds(Collection<P> list) {
        return removeBatchByIds(list, defaultBatchSize);
    }

    /**
     * 批量删除(jdbc批量提交)
     *
     * @param list    主键ID或实体列表(主键ID类型必须与实体类型字段保持一致)
     * @param useFill 是否启用填充(为true的情况,会将入参转换实体进行delete删除)
     * @return 删除结果
     * @since 3.5.0
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean removeBatchByIds(Collection<P> list, boolean useFill) {
        return removeBatchByIds(list, defaultBatchSize, useFill);
    }

    /**
     * 批量删除(jdbc批量提交)
     *
     * @param list      主键ID或实体列表
     * @param batchSize 批次大小
     * @return 删除结果
     * @since 3.5.0
     */
    public boolean removeBatchByIds(Collection<P> list, int batchSize) {
        return this.removeBatchByIds(list, batchSize, false);
    }

    /**
     * 批量删除(jdbc批量提交)
     *
     * @param list      主键ID或实体列表
     * @param batchSize 批次大小
     * @param useFill   是否启用填充(为true的情况,会将入参转换实体进行delete删除)
     * @return 删除结果
     * @since 3.5.0
     */
    public boolean removeBatchByIds(Collection<P> list, int batchSize, boolean useFill) {
        if (CollectionUtils.isEmpty(list)) {
            return false;
        }
        return SqlHelper.retBool(getBaseMapper().deleteBatchIds(CollUtil.sub(list, 0, batchSize)));
    }

    /**
     * 根据 ID 选择修改
     *
     * @param entity 实体对象
     */
    public boolean updateById(T entity) {
        if (Objects.isNull(entity.getId())) {
            throw new IllegalArgumentException("Id 不能为空！");
        }
        return SqlHelper.retBool(getBaseMapper().updateById(entity));
    }

    /**
     * 根据 UpdateWrapper 条件，更新记录 需要设置sqlset
     *
     * @param updateWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper}
     */
    public boolean update(Wrapper<T> updateWrapper) {
        return update(null, updateWrapper);
    }

    /**
     * 根据 whereEntity 条件，更新记录
     *
     * @param entity        实体对象
     * @param updateWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper}
     */
    public boolean update(T entity, Wrapper<T> updateWrapper) {
        return SqlHelper.retBool(getBaseMapper().update(entity, updateWrapper));
    }

    /**
     * 根据ID 批量更新
     *
     * @param entityList 实体对象集合
     */
    @Transactional(rollbackFor = Exception.class)
    public boolean updateBatchById(Collection<T> entityList) {
        return updateBatchById(entityList, defaultBatchSize);
    }

    /**
     * 根据ID 批量更新
     *
     * @param entityList 实体对象集合
     * @param batchSize  更新批次数量
     * @return 是否成功
     */
    public abstract boolean updateBatchById(Collection<T> entityList, int batchSize);

    /**
     * TableId 注解存在更新记录，否插入一条记录
     *
     * @param entity 实体对象
     */
    @Transactional(rollbackFor = Exception.class)
    @SuppressWarnings("unchecked")
    public boolean saveOrUpdate(T entity) {
        if (null != entity) {
            TableInfo tableInfo = TableInfoHelper.getTableInfo(this.getEntityClass());
            Assert.notNull(tableInfo, "error: can not execute. because can not find cache of TableInfo for entity!");
            String keyProperty = tableInfo.getKeyProperty();
            Assert.notEmpty(keyProperty, "error: can not execute. because can not find column for id from entity!");
            P idVal = (P) tableInfo.getPropertyValue(entity, tableInfo.getKeyProperty());
            return StringUtils.checkValNull(idVal) || Objects.isNull(findById(idVal)) ? save(entity) : updateById(entity);
        }
        return false;
    }

    /**
     * 根据 ID 查询
     *
     * @param id 主键ID
     */
    public T findById(P id) {
        return getBaseMapper().selectById(id);
    }

    /**
     * 查询（根据ID 批量查询）
     *
     * @param idList 主键ID列表
     */
    @NonNull
    public List<T> findByIds(Collection<P> idList) {
        if (CollUtil.isEmpty(idList)) {
            return Collections.emptyList();
        }
        return CollUtil.emptyIfNull(getBaseMapper().selectBatchIds(idList));
    }

    /**
     * 查询（根据 columnMap 条件）
     *
     * @param columnMap 表字段 map 对象
     */
    public List<T> findByMap(Map<String, Object> columnMap) {
        return getBaseMapper().selectByMap(columnMap);
    }

    /**
     * 查询总记录数
     *
     * @see Wrappers#emptyWrapper()
     */
    public long count() {
        return count(Wrappers.emptyWrapper());
    }

    /**
     * 根据 Wrapper 条件，查询总记录数
     *
     * @param queryWrapper 实体对象封装操作类 {@link QueryWrapper}
     */
    public long count(Wrapper<T> queryWrapper) {
        return SqlHelper.retCount(getBaseMapper().selectCount(queryWrapper));
    }

    /**
     * 根据ID集合查询总记录数
     *
     * @param ids ID集合
     * @return 总记录数
     */
    public long countByIds(Collection<P> ids) {
        return this.count(Wrappers.<T>query().in(TableInfoHelper.getTableInfo(getEntityClass()).getKeyColumn(), ids));
    }

    /**
     * 查询列表
     *
     * @param queryWrapper 实体对象封装操作类 {@link QueryWrapper}
     */
    public List<T> findAll(Wrapper<T> queryWrapper) {
        return getBaseMapper().selectList(queryWrapper);
    }

    /**
     * 查询列表
     *
     * @param queryExample 实体例子对象
     */
    public List<T> findAll(T queryExample) {
        Wrapper<T> queryWrapper = Wrappers.query(queryExample);
        return this.findAll(queryWrapper);
    }

    /**
     * 查询所有
     *
     * @see Wrappers#emptyWrapper()
     */
    public List<T> findAll() {
        return findAll(Wrappers.emptyWrapper());
    }

    /**
     * 翻页查询
     *
     * @param page         翻页对象
     * @param queryWrapper 实体对象封装操作类 {@link QueryWrapper}
     */
    public <E extends IPage<T>> E page(E page, Wrapper<T> queryWrapper) {
        return getBaseMapper().selectPage(page, queryWrapper);
    }

    /**
     * 无条件翻页查询
     *
     * @param page 翻页对象
     * @see Wrappers#emptyWrapper()
     */
    public <E extends IPage<T>> E page(E page) {
        return page(page, Wrappers.emptyWrapper());
    }

    /**
     * 查询列表
     *
     * @param queryWrapper 实体对象封装操作类 {@link QueryWrapper}
     */
    public List<Map<String, Object>> findAllForMaps(Wrapper<T> queryWrapper) {
        return getBaseMapper().selectMaps(queryWrapper);
    }

    /**
     * 查询所有列表
     *
     * @see Wrappers#emptyWrapper()
     */
    public List<Map<String, Object>> findAllForMaps() {
        return findAllForMaps(Wrappers.emptyWrapper());
    }

    /**
     * 翻页查询
     *
     * @param page         翻页对象
     * @param queryWrapper 实体对象封装操作类 {@link QueryWrapper}
     */
    public <E extends IPage<Map<String, Object>>> E findAllForMaps(E page, Wrapper<T> queryWrapper) {
        return getBaseMapper().selectMapsPage(page, queryWrapper);
    }

    /**
     * 获取对应 entity 的 BaseMapper
     *
     * @return BaseMapper
     */
    public abstract BaseMapper<T> getBaseMapper();

    /**
     * 获取 entity 的 class
     *
     * @return {@link Class<T>}
     */
    public abstract Class<T> getEntityClass();

    /**
     * 链式查询 普通
     * 以下的方法使用介绍:
     * <p>
     * 一. 名称介绍
     * 1. 方法名带有 query 的为对数据的查询操作, 方法名带有 update 的为对数据的修改操作
     * 2. 方法名带有 lambda 的为内部方法入参 column 支持函数式的
     * 二. 支持介绍
     * <p>
     * 1. 方法名带有 query 的支持以 {@link ChainQuery} 内部的方法名结尾进行数据查询操作
     * 2. 方法名带有 update 的支持以 {@link ChainUpdate} 内部的方法名为结尾进行数据修改操作
     * <p>
     * 三. 使用示例,只用不带 lambda 的方法各展示一个例子,其他类推
     * 1. 根据条件获取一条数据: `query().eq("column", value).one()`
     * 2. 根据条件删除一条数据: `update().eq("column", value).remove()`
     *
     * @return QueryWrapper 的包装类
     */
    public QueryChainWrapper<T> filter() {
        return ChainWrappers.queryChain(getBaseMapper());
    }

    /**
     * 链式查询 lambda 式
     * <p>注意：不支持 Kotlin </p>
     *
     * @return LambdaQueryWrapper 的包装类
     */
    public LambdaQueryChainWrapper<T> lambda() {
        return ChainWrappers.lambdaQueryChain(getBaseMapper());
    }

    /**
     * 链式更改 普通
     *
     * @return UpdateWrapper 的包装类
     */
    public UpdateChainWrapper<T> update() {
        return ChainWrappers.updateChain(getBaseMapper());
    }

    /**
     * 链式更改 lambda 式
     * <p>注意：不支持 Kotlin </p>
     *
     * @return LambdaUpdateWrapper 的包装类
     */
    public LambdaUpdateChainWrapper<T> lambdaUpdate() {
        return ChainWrappers.lambdaUpdateChain(getBaseMapper());
    }

    /**
     * <p>
     * 根据updateWrapper尝试更新，否继续执行saveOrUpdate(T)方法
     * 此次修改主要是减少了此项业务代码的代码量（存在性验证之后的saveOrUpdate操作）
     * </p>
     *
     * @param entity 实体对象
     */
    public boolean saveOrUpdate(T entity, Wrapper<T> updateWrapper) {
        return update(entity, updateWrapper) || saveOrUpdate(entity);
    }

    /**
     * 执行批量操作
     *
     * @param list      数据集合
     * @param batchSize 批量大小
     * @param consumer  执行方法
     * @param <E>       泛型
     * @return 操作结果
     * @since 3.3.1
     */
    protected <E> boolean executeBatch(Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) {
        return SqlHelper.executeBatch(getEntityClass(), this.log, list, batchSize, consumer);
    }

    /**
     * 链式查询 普通
     *
     * @return QueryWrapper 的包装类
     */
    public MPJQueryWrapper<T> filter(Class<T> eoClazz) {
        return com.github.yulichang.toolkit.JoinWrappers.query(eoClazz).selectAll(eoClazz);
    }

    /**
     * 根据实例查询1条记录
     *
     * @param example 查询实例
     * @return 查询结果
     */
    @Nullable
    public T findOne(@Nullable T example) {
        QueryWrapper<T> query = Objects.nonNull(example) ? Wrappers.query(example) : new QueryWrapper<>();
        DasHelper.addBaseUniqueSort(query);
        query.last(getFirstSqlSegment());
        return getBaseMapper().selectOne(query);
    }

    /**
     * 构建查询数据的第一个条目的SQL片段
     *
     * @return SQL片段
     */
    protected String getFirstSqlSegment() {
        if (isSupportsMySqlSyntax()) {
            return " LIMIT 1 ";
        } else if (isSupportsOracleSyntax()) {
            return " FETCH FIRST 1 ROWS ONLY ";
        }
        throw new UnsupportedOperationException("不支持的数据库类型");
    }

    /**
     * 是否可以使用MySQL语法
     *
     * @return 如果支持，返回true，否则返回false
     */
    protected boolean isSupportsMySqlSyntax() {
        return DasHelper.isSupportsMySqlSyntax(dataSource);
    }

    /**
     * 是否可以使用Oracle语法
     *
     * @return 如果支持，返回true，否则返回false
     */
    protected boolean isSupportsOracleSyntax() {
        return DasHelper.isSupportsOracleSyntax(dataSource);
    }

    /**
     * 查询1条记录
     *
     * @return 查询结果
     */
    @Nullable
    public T findOne() {
        return findOne(null);
    }

    /**
     * 查询最后1条记录
     *
     * @return 查询结果
     */
    @Nullable
    public T findLast() {
        return findOne();
    }

    /**
     * 根据实例查询1条记录
     *
     * @param example 查询实例
     * @return 查询结果
     */
    public Optional<T> findOneOpt(@Nullable T example) {
        return Optional.ofNullable(findOne(example));
    }

    /**
     * 查询1条记录
     *
     * @return 查询结果
     */
    public Optional<T> findOneOpt() {
        return Optional.ofNullable(findOne());
    }

    /**
     * 根据实例查询最后1条记录
     *
     * @param example 查询实例
     * @return 查询结果
     */
    @Nullable
    public T findLast(@Nullable T example) {
        return findOne(example);
    }

    /**
     * 根据实例查询最后1条记录
     *
     * @param example 查询实例
     * @return 查询结果
     */
    public Optional<T> findLastOpt(@Nullable T example) {
        return Optional.ofNullable(findLast(example));
    }

    /**
     * 查询最后1条记录
     *
     * @return 查询结果
     */
    public Optional<T> findLastOpt() {
        return Optional.ofNullable(findLast());
    }

    /**
     * 获取实体表名
     *
     * @return 表名
     */
    protected String getTableName() {
        return TableInfoHelper.getTableInfo(getEntityClass()).getTableName();
    }

    /**
     * 获取eo所有属性名和字段名的映射map
     *
     * @return 映射map，key为属性名，value为字段名
     */
    Map<String, String> getColumnPropNameMap() {
        LinkedHashMap<String, String> columnPropNameMap = new LinkedHashMap<>();
        TableInfo tableInfo = TableInfoHelper.getTableInfo(getEntityClass());
        if (tableInfo.havePK()) {
            columnPropNameMap.put(tableInfo.getKeyProperty(), tableInfo.getKeyColumn());
        }
        columnPropNameMap.putAll(tableInfo.getFieldList().stream()
            // 有序的map
            .collect(Collectors.toMap(TableFieldInfo::getProperty, TableFieldInfo::getColumn, (k1, k2) -> k1, LinkedHashMap::new)));
        return columnPropNameMap;
    }
}
